本系列文章會在筆者的部落格繼續連載!Design System 101 感謝大家的閱讀!
原本今日計畫寫的內容是關於 Design System 文檔建置,但鑑於上一篇組件也是 Accessibility 相關的 Visually Hidden,決定一氣呵成介紹另一個 Accessibility 相關的組件 - FocusScope。
繼上次介紹完 Accessibility 後,了解到建立無障礙網頁的重要性,且明白使用者不一定是透過滑鼠來操作網頁,或許是透過鍵盤來操作,所以今天要來介紹一個在鍵盤操作上的重要概念 - Focus Management.
在討論 Focus Scope 之前,我們首先要了解什麼是「focus」。簡單來說,「focus」是 UI 中的一個重要概念,它指的是當前正在被使用者操作或對話的元素。
Accessible focus management is the practice of using programmatic focus changes to enhance comprehension and usability of a website. -- Cloudscape.design
Focus Scope 顧名思義,就是將 focus 限制在某個範圍內。最常看見的例子就是用在 Modal 組件上。當 Modal 打開時,我們希望使用者只能 focus 在 Modal 內的元素,而不能 focus 到 Modal 以外的元素。
想像一下,當今天我們是透過鍵盤來操作介面的使用者,我們可能需要透過 tab
鍵 focus 到下一個 focusable 的元素。
畢竟網頁不可能只有一層 Layer。可能你點擊某個 Button
會跳出 Modal
, Dropdown Menu
等等對話窗形式的組件。對於滑鼠的使用者,他們可以很輕鬆自然地互動這類型的組件。
但對於鍵盤使用者,如果沒有自動將可 focus 的範圍限縮到對話窗式的組件內,則使用者將無法互動到這些組件內的元素。
因此,開發者通常會在這些組件上加入 <FocusScope>
。當對話窗形式的組件打開時,自動的 focus 到組件內的元素,而關掉組件時則 restore 到原本的 Button
元素。
接下來,我將介紹如何設計一個 FocusScope 組件,並且提供一個 hook 讓其他開發者可以透過它來控制 focus 的行為。
整理一下我們目前的需求:
tab
去 focus 元素時,不會 focus 到 Modal 以外的元素
*舉 Radix UI 的 Dialog 為例,一個好的 Dialog 應該要可以透過鍵盤操作完成所有行為
如果將上述的問題拆解,可以將問題拆解成兩個部分:
tab
等鍵盤事件控制是否 focus 到下一個元素)若要提供一個 FocusScope 的組件來解決上述問題,應該要如何設計並且如何應用此組件呢?
為了取得特定範圍內的 focusable 元素,我們可以用 <span hidden />
去包住範圍內的元素。並透過迭代所有的子元素,將 focusable 的元素保存在 state 內。接著,我們可以透過 React Context API 將 focus 的操作傳遞下去。
<FocusScope>
<Component />
</FocusScope>
並且提供一個 hook 讓其他開發者可以透過它來控制 focus 的行為。
const Component = () => {
const focusManager = getFocusManager();
const handleKeyDown = (e) => {
if (e.key === 'ArrowLeft') focusManager.focusNext();
};
// ...
};
參數 | 型別 | 說明 |
---|---|---|
children |
ReactNode | 容器的內容 |
restoreFocus |
boolean | 是否恢復到原始焦點 |
autoFocus |
boolean | 是否自動 focus 內容元素 |
contain |
boolean | 是否將 focus 限制在容器內 |
明天會介紹如何實作,大家不仿先嘗試看看實作一個 FocusScope 組件吧!
> design-system/ pnpm generate // name: focus-scope
> design-system/ cd packages/focus-scope
> design-system/packages/focus-scope/ pnpm i // 安裝相依套件
> design-system/ pnpm run test -w
> design-system/ pnpm run storybook
> design-system/ pnpm changeset